<?php
namespace WDB\SQLDriver\Result;
use WDB,
    WDB\Exception;

/**
 *
 * @author Richard Ejem <richard(at)ejem.cz>
 * @package WDB
 */
final class MySQL_SelectResult extends WDB\BaseObject implements WDB\Query\iSelectResult
{
    /**@var \mysqli_result*/
    private $resultObject;
    /**@var WDB\Query\Select*/
    private $query;
    /**@var WDB\Database*/
    private $database = NULL;
    
    /**@return WDB\Database|NULL*/
    public function getDatabase()
    {
        return $this->database;
    }
    
    /**@return WDB\Query\Select*/
    public function getQuery()
    {
        return $this->query;
    }
    
    /**@var int $offset */
    private $offset;
    
    /**@var stdClass[] $fields*/
    private $fields;
    
    /**
     * @param WDB\Query\Select original select query
     * @param \mysqli_result mysqli result object
     */
    public function __construct(WDB\Query\Select $query, \mysqli_result $resultObject)
    {
        $this->resultObject = $resultObject;
        $this->query = $query;
        $this->offset = 0;
        $fields = $this->resultObject->fetch_fields();
        $this->fields = array();
        foreach ($fields as $field)
        {
            $this->fields[$field->name] = $field;
        }
    }
    
    // <editor-fold defaultstate="collapsed" desc="iSelectResult implementation">
    public function bindDatabase (WDB\Database $db)
    {
        $this->database = $db;
    }
    
    public function singleValue()
    {
        return isset($this[0]) && isset($this[0][0]) ? $this[0][0] : NULL;
    }
    
    public function singleRow()
    {
        return $this[0];
    }
    
    public function allRows()
    {
        $result = array();
        foreach ($this as $row)
        {
            $result[] = $row;
        }
        return $result;
    }

    public function getForeignKeys()
    {
        $foreignKeys = array();
        if ($this->database === NULL) throw new Exception\InvalidOperation("select result has not bound database connection.");
        $schema = $this->database->getSchema();
        $fields = array();
        //first create mapping which database columns are present in the result and what are their names
        foreach ($this->fields as $field)
        {
            if ($field->orgtable && !isset($fields[$field->orgtable]))
            {
                $fields[$field->orgtable] = array();
            }
            if ($field->orgname) $fields[$field->orgtable][$field->orgname] = $field->name;
        }
        //then, read foreign keys leading from these columns
        foreach ($this->fields as $field)
        {
            if (isset($schema->tables[$field->orgtable]) && isset($schema->tables[$field->orgtable]->columns[$field->orgname]))
            {
                
                $fk = $schema->tables[$field->orgtable]->columns[$field->orgname]->foreignKeys;
                foreach ($fk as $fkey)
                {
                    //check if all columns of the current foreign key are present in result
                    //and get their names
                    $success = true;
                    foreach ($fkey->columns as $column)
                    {
                        if (!isset($fields[$field->orgtable][$column]))
                        {
                            $success = false;
                            break;
                        }
                    }
                    if ($success)
                    {
                        $foreignKeys[$fkey->name] = $fkey;
                    }
                }
            }
        }
        return $foreignKeys;
    }
    
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="Countable implementation">
    public function count()
    {
        return $this->resultObject->num_rows;
    }
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="ArrayAccess implementation">
    public function offsetExists($offset)
    {
        return is_numeric($offset) && $offset < $this->resultObject->num_rows;
    }

    /**
     *
     * @param int offset
     * @return WDB\Query\SelectedRow 
     * @throws Exception\OutOfBounds on non-existent offset
     */
    public function offsetGet($offset)
    {
        if (!$this->offsetExists($offset)) throw new Exception\OutOfBounds("offset $offset does not exist");
        $this->resultObject->data_seek($offset);
        $return = $this->fetchResult();
        $this->resultObject->data_seek($this->offset);
        return new WDB\Query\SelectedRow($this, $return);
    }

    /**
     * Invalid operation on database result.
     *
     * @param int offset
     * @parma mixed value
     * @throws Exception\InvalidOperation
     */
    public function offsetSet($offset, $value)
    {
        throw new Exception\InvalidOperation("database result is read only");
    }

    /**
     * Invalid operation on database result.
     *
     * @param int offset
     * @throws Exception\InvalidOperation
     */
    public function offsetUnset($offset)
    {
        throw new Exception\InvalidOperation("database result is read only");
    }
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="Iterator implementation">
    public function current()
    {
        $data = $this->fetchResult();
        $result = new WDB\Query\SelectedRow($this, $data);
        $this->resultObject->data_seek($this->offset);
        return $result;
    }
    

    public function key()
    {
        return $this->offset;
    }

    public function next()
    {
        ++$this->offset;
        return new WDB\Query\SelectedRow($this, $this->fetchResult());
    }

    public function rewind()
    {
        $this->offset = 0;
        $this->resultObject->data_seek(0);
    }

    public function valid()
    {
        return ($this->offset >= 0 && $this->offset < $this->count());
    }
    // </editor-fold>
    
    private function fetchResult()
    {
        $row = $this->resultObject->fetch_assoc();
        foreach ($row as $key=>&$val)
        {
            if ($val === NULL) continue;
            if ($this->fields[$key]->flags & 2048) //SET type
            {
                if ($val === '')
                {
                    $val = array();
                }
                else
                {
                    $val = explode(',', $val);
                }
            }
            if (in_array($this->fields[$key]->type, array(7,10,12))) //date types
            {
                $val = new \DateTime($val);
            }
            if ($this->fields[$key]->type == 11) //time type
            {
                $val = new \DateInterval("P0000-00-00T".$val);
            }
        }
        unset($val); // fixes PHP bug with referenced foreach
        return $row;
    }
}